package protoc

import (
	"path"
	"strings"
)

// ProtocConfiguration represents the complete configuration and source
// mappings.
type ProtocConfiguration struct {
	// PackageConfig parent
	PackageConfig *PackageConfig
	// The config for the p
	LanguageConfig *LanguageConfig
	// the workspace relative path of the BUILD file where this rule is being
	// generated.
	Rel string
	// the prefix for the rule (e.g. 'java')
	Prefix string
	// the library thar holds the proto files
	Library ProtoLibrary
	// the configuration for the plugins
	Plugins []*PluginConfiguration
	// The merged set of Source files for the compilations
	Outputs []string
	// The merged set of imports for the compilations
	Imports []string
	// The generated source mappings
	Mappings map[string]string
}

func newProtocConfiguration(pc *PackageConfig, lc *LanguageConfig, workDir, rel, prefix string, lib ProtoLibrary, plugins []*PluginConfiguration) *ProtocConfiguration {
	srcs, mappings := mergeSources(workDir, rel, plugins, lib.StripImportPrefix())

	return &ProtocConfiguration{
		PackageConfig:  pc,
		LanguageConfig: lc,
		Rel:            rel,
		Prefix:         prefix,
		Library:        lib,
		Plugins:        plugins,
		Outputs:        srcs,
		Mappings:       mappings,
	}
}

func (c *ProtocConfiguration) GetPluginConfiguration(implementationName string) *PluginConfiguration {
	for _, plugin := range c.Plugins {
		if plugin.Config.Implementation == implementationName {
			return plugin
		}
	}
	return nil
}

func (c *ProtocConfiguration) GetPluginOutputs(implementationName string) []string {
	plugin := c.GetPluginConfiguration(implementationName)
	if plugin == nil {
		return nil
	}
	return plugin.Outputs
}

// mergeSources computes the source files that are generated by the rule and any
// necessary mappings.
func mergeSources(workDir, rel string, plugins []*PluginConfiguration, stripImportPrefix string) ([]string, map[string]string) {
	srcs := make([]string, 0)
	mappings := make(map[string]string)

	// if the stripImportPrefix is defined and "absolute" (starting with a
	// slash), this means it is relative to the repository root.
	// https://github.com/bazelbuild/bazel/issues/3867#issuecomment-441971525
	prefix := strings.TrimPrefix(stripImportPrefix, "/")

	for _, plugin := range plugins {

		// if plugin provided mappings for us, use those preferentially
		if len(plugin.Mappings) > 0 {
			srcs = append(srcs, plugin.Outputs...)

			for k, v := range plugin.Mappings {
				mappings[k] = v
			}
			continue
		}

		// otherwise, fallback to baseline method
		for _, filename := range plugin.Outputs {
			if prefix != "" {
				filename = strings.TrimPrefix(filename, prefix)
			}
			dir := path.Dir(filename)
			if dir == "." && rel == "" {
				dir = rel
			}
			if dir == rel {
				// no mapping required, just add to the srcs list
				srcs = append(srcs, strings.TrimPrefix(filename, rel+"/"))
			} else {
				// add the basename only to the srcs list and add a mapping.
				base := path.Base(filename)
				mappings[base] = filename
				srcs = append(srcs, base)
			}
		}
	}

	return srcs, mappings
}
